/*
 * Class
 * PoissonDistribution
 */

package pl.abstractvoid.distributions;

import java.util.Random;
import java.util.TreeMap;
import pl.abstractvoid.datamodel.parameters.ParameterSet;
import pl.abstractvoid.datamodel.parameters.SingleParameter;
import pl.abstractvoid.datamodel.parameters.exceptions.NoSuchParameterException;
import pl.abstractvoid.distributions.exceptions.ParameterValidationFailException;
import pl.abstractvoid.rconnector.RCallerInstance;
import pl.abstractvoid.rconnector.RInterpreter;
import rcaller.exception.ParseException;

/**
 * Represents Poisson distribution.
 * 
 * @author Wojciech Szałapski
 */
public class PoissonDistribution extends AbstractDistribution {

    /**
     * String representing the name of the variable which stores the expected
     * value in R.
     */
    private final static String expectedValueString = "expectedValue";
    /**
     * Names for output values used by R interpreter.
     */
    private final static String[] outputTableNames;
    /**
     * Names for output values used by user interface.
     */
    private final static String[] outputTableVisibleNames;
    /**
     * Buffer for the script executed when the output is computed.
     */
    private final static StringBuilder poissonSetUpOutputScript = new StringBuilder();
    /**
     * Buffer for the script executed when the cumulative distribution plot is
     * generated.
     */
    private final static StringBuilder poissonSetUpCumulativeDistributionPlotScript = new StringBuilder();
    /**
     * Buffer for the script executed when the probability density/mass function 
     * plot is generated.
     */
    private final static StringBuilder poissonSetUpProbabilityPlotScript = new StringBuilder();
    
    /*
     * Initialization of:
     *  - Names of output parameters.
     *  - Script for computing output parameters.
     *  - Scripts for generating plots.
     */
    static {
        final int numberOfParameters = 2;
        outputTableNames = new String[numberOfParameters];
        outputTableVisibleNames = new String[numberOfParameters];
        outputTableNames[0] = "variance";
        outputTableNames[1] = "standardDeviation";
        
        poissonSetUpOutputScript.append(outputTableNames[0]).append(" = ").
                append(RInterpreter.inputRList).append("$").append(expectedValueString).
                append("\n").append(outputTableNames[1]).append(" = ").
                append(outputTableNames[0]).append("^(1/2)\n");
        poissonSetUpOutputScript.append(RInterpreter.outputRList).append(" = list(");
        String prefix = "";
        for (String name : outputTableNames) {
            poissonSetUpOutputScript.append(prefix).append(name).append(" = ").append(name);
            prefix = ", ";
        }
        poissonSetUpOutputScript.append(")");
        
        poissonSetUpCumulativeDistributionPlotScript.append(
                "xMinimumValue = 0\n").
                append("xMaximumValue = 1 + qpois(0.9999, ").
                append(RInterpreter.inputRList).append("$").append(expectedValueString).append(")\n").
                append("plotX = c(xMinimumValue:xMaximumValue)\n"
                + "normalPlot(plotX, ppois(plotX, ").
                append(RInterpreter.inputRList).append("$").append(expectedValueString).append("), "
                + "type = 's')");
        
        poissonSetUpProbabilityPlotScript.append(poissonSetUpCumulativeDistributionPlotScript.
                toString().replaceAll("ppois", "dpois").replaceAll("type = 's'", "type = 'p'"));
    }
    
    /**
     * Constructs the distribution using given input parameters.
     * 
     * @param expectedValue Expected value, i. e. mean (equal to variance).
     * @throws ParameterValidationFailException
     * @throws NoSuchParameterException 
     */
    public PoissonDistribution(double expectedValue)
            throws ParameterValidationFailException, NoSuchParameterException {
        super(poissonSetUpOutputScript, poissonSetUpCumulativeDistributionPlotScript, 
              poissonSetUpProbabilityPlotScript);
        distributionName = "poissonDistribution";
        rState = new RCallerInstance();
        SingleParameter expectedValueParam = new SingleParameter(expectedValueString);
        expectedValueParam.setParameterValue(expectedValue);
        TreeMap<String, SingleParameter> singleParameters = new TreeMap<>();
        singleParameters.put(expectedValueString, expectedValueParam);
        ParameterSet parameterSet = new ParameterSet();
        parameterSet.setSingleParameters(singleParameters);
        updateInput(parameterSet);
    }

    @Override
    protected void validate(ParameterSet parameters) throws ParameterValidationFailException {
        TreeMap<String, SingleParameter> singleParameters = parameters.getSingleParameters();
        SingleParameter expectedValueParam = singleParameters.get(expectedValueString);
        if (expectedValueParam.getParameterValue() <= 0) {
            throw new ParameterValidationFailException("nonPositiveExpectedValue");
        }
        if (expectedValueParam.getParameterValue() > 2e6) {
            throw new ParameterValidationFailException("tooBigExpectedValue");
        }
    }

    @Override
    public ParameterSet loadParameters() throws NoSuchParameterException, ParseException {
        rState.runCustomCode("", RInterpreter.inputRList);
        SingleParameter expectedValueParam = new SingleParameter(expectedValueString);
        expectedValueParam.setParameterValue(getSingleParameterValue(expectedValueString));
        TreeMap<String, SingleParameter> singleParameters = new TreeMap<>();
        singleParameters.put(expectedValueString, expectedValueParam);
        ParameterSet result = new ParameterSet();
        result.setSingleParameters(singleParameters);
        return result;
    }

    @Override
    protected void initializeOutputTable() {
        outputTable = new SingleParameter[outputTableNames.length];
        for (int i = 0; i < outputTableNames.length; ++i) {
            outputTable[i] = new SingleParameter(outputTableNames[i]);
        }
    }
    
    /**
     * Returns a new instance of a Poisson distribution with random expected
     * value (from 0.001 to 10).
     * 
     * @return New instance of normal distribution with random parameters.
     */
    public static PoissonDistribution getSampleDistributionData() {
        Random generator = new Random();
        double expectedValue = generator.nextDouble() * 10 + 0.001;
        try {
            return new PoissonDistribution(expectedValue);
        } catch (ParameterValidationFailException | NoSuchParameterException ex) {
            return null;
        }
    }
}
